package cn.tth.test.game.scan;

//String locationPattern = "classpath*:cn/tth/test/**/*.class";

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;

/**
 * 注释扫描
 *
 * @author Yanyuhao
 * @date 2022/05/26
 */
public class AnnotationScan {

    public static final String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
    public static final String CLASS_SUFFIX = "/**/*.class";

    public static List<String> getAllPathUrl(String path) throws IOException {
        String replace = path.replace(".", "\\");
        String locationPattern = CLASSPATH_ALL_URL_PREFIX + replace + CLASS_SUFFIX;
        Resource[] resources = findPathMatchingResources(locationPattern);
        List<String> classNameList = new ArrayList<>();
        for (Resource resource : resources) {
            String className = getClassName(resource);
            classNameList.add(className);
        }
        return classNameList;
    }

    private static String getClassName(Resource resource) throws IOException {
        String path = resource.getFile().getPath();
        int lastIndexOf = path.lastIndexOf("classes") + 8;
        String substring = path.substring(lastIndexOf);
        String substring1 = substring.substring(0, substring.length() - 6);
        return substring1.replace("\\", ".");
    }

    public static Resource[] findPathMatchingResources(String locationPattern) throws IOException {
        String rootDirPath = determineRootDir(locationPattern);
        String subPattern = locationPattern.substring(rootDirPath.length());
        Resource[] rootDirResources = getResources(rootDirPath);
        Set<Resource> result = new LinkedHashSet<>(16);
        for (Resource rootDirResource : rootDirResources) {
            result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
        }
        return result.toArray(new Resource[0]);
    }

    protected static Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) {
        File rootDir;
        try {
            rootDir = rootDirResource.getFile().getAbsoluteFile();
        } catch (Exception ex) {
            return Collections.emptySet();
        }
        return doFindMatchingFileSystemResources(rootDir, subPattern);
    }

    protected static Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) {
        Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
        Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
        for (File file : matchingFiles) {
            result.add(new FileSystemResource(file));
        }
        return result;
    }

    protected static Set<File> retrieveMatchingFiles(File rootDir, String pattern) {
        if (!rootDir.exists()) {
            return Collections.emptySet();
        }
        if (!rootDir.isDirectory()) {
            return Collections.emptySet();
        }
        if (!rootDir.canRead()) {
            return Collections.emptySet();
        }
        String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
        if (!pattern.startsWith("/")) {
            fullPattern += "/";
        }
        fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
        Set<File> result = new LinkedHashSet<>(8);
        doRetrieveMatchingFiles(fullPattern, rootDir, result);
        return result;
    }

    protected static void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) {
        for (File content : listDirectory(dir)) {
            String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
            if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
                if (content.canRead()) {
                    doRetrieveMatchingFiles(fullPattern, content, result);
                }
            }
            if (getPathMatcher().match(fullPattern, currPath)) {
                result.add(content);
            }
        }
    }

    protected static File[] listDirectory(File dir) {
        File[] files = dir.listFiles();
        if (files == null) {
            return new File[0];
        }
        Arrays.sort(files, Comparator.comparing(File::getName));
        return files;
    }

    public static String determineRootDir(String location) {
        int prefixEnd = location.indexOf(':') + 1;
        int rootDirEnd = location.length();
        while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
            rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
        }
        if (rootDirEnd == 0) {
            rootDirEnd = prefixEnd;
        }
        return location.substring(0, rootDirEnd);
    }

    private static PathMatcher getPathMatcher() {
        return new AntPathMatcher();
    }

    public static Resource[] getResources(String locationPattern) throws IOException {
        return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
    }

    public static Resource[] findAllClassPathResources(String location) throws IOException {
        String path = location;
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        Set<Resource> result = doFindAllClassPathResources(path);

        return result.toArray(new Resource[0]);
    }

    protected static Set<Resource> doFindAllClassPathResources(String path) throws IOException {
        Set<Resource> result = new LinkedHashSet<>(16);
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            result.add(convertClassLoaderURL(url));
        }
        return result;
    }

    protected static Resource convertClassLoaderURL(URL url) {
        return new UrlResource(url);
    }
}
